//
// Copyright (C) 2011 Cornell University, Department of Computer Science
//
// File Name: SessionTableManager.java
// Author Name: Poornima Prabhu
// Date: 7:16:45 PM
package edu.cornell.cs.lsi.projone.service;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.LogFactoryImpl;

import edu.cornell.cs.lsi.projone.common.LSIConstants;
import edu.cornell.cs.lsi.projone.common.LSIException;
import edu.cornell.cs.lsi.projone.model.SessionTableData;

public class SessionTableManager extends Thread {

	private final static Log LOGGER = LogFactoryImpl
			.getLog(SessionTableManager.class);

	/**
	 * Data structure to store the session entries.
	 */
	private static Map<String, SessionTableData> sessionMap = new HashMap<String, SessionTableData>();
	private final long sessionTimeout = 120000;
	private static long sessionId = 1;
	private static SessionTableManager sessionTableMgr = new SessionTableManager();
	private static boolean isthreadStarted = false;
	private static RPCClientService rpcClient;

	public SessionTableManager() {
		try {
			rpcClient = new RPCClientService();
		} catch (LSIException e) {
			LOGGER.error("Error while starting the client service");
			e.printStackTrace();
		}
	}

	public void startThread() {
		if (!isthreadStarted) {
			sessionTableMgr.start();
			isthreadStarted = true;
		}
	}

	// overridden run method
	// remove the timedout session frm session table data

	@Override
	public void run() {

		isthreadStarted = true;

		try {
			while (sessionMap.size() != 0) {

				Set<String> keys = sessionMap.keySet();
				for (String key : keys) {
					SessionTableData user = sessionMap.get(key);
					String sessionTime = user.getSessionCreationTime();
					if (isTimedOut(sessionTime, "", sessionTimeout)
							&& !user.isInUse()) {
						synchronized (this) {
							sessionMap.remove(key);
						}
					}
				}
				this.wait(sessionTimeout);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// difference between dates in msec
	public static boolean isTimedOut(String oldDate, String newDate,
			long timeOut) {
		System.out.println("------------- old" + oldDate);
		System.out.println("------------- new" + newDate);
		System.out.println("------------- timeout" + timeOut);
		try {
			DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
			Date date1 = dateFormat.parse(oldDate);
			Date date2 = dateFormat.parse(newDate);

			if (date2.getTime() - date1.getTime() > timeOut)
				return true;
			else
				return false;

		} catch (ParseException e) {

			System.out.println("Exception :" + e);

		}
		return false;
	}

	public Map<String, SessionTableData> getSessionMap() {
		return sessionMap;
	}

	public boolean containsSession(String sessionId) {
		return sessionMap.containsKey(sessionId);
	}

	public static synchronized long getSessionCounter() {
		return sessionId;
	}

	public static synchronized void setSessionCounter(long number) {
		sessionId = number;
	}

	/**
	 * This method is used to add the data into the Session map.
	 */
	public void addEntry(SessionTableData sessionData) {
		if (sessionData == null || sessionData.getSessionID() == null
				|| "".equals(sessionData.getSessionID().trim())) {
			System.out
					.println("ERROR: Incoming session data entry is null or empty.");
			return;
		}
		// if (!sessionMap.containsKey(sessionData.getSessionID()))
		sessionMap.put(sessionData.getSessionID(), sessionData);

	}

	/**
	 * Method to fetch an entry from the session map based on the session id.
	 */
	public SessionTableData getEntry(String sessionId, double sessionVer) {

		SessionTableData sessionData = null;
		if (sessionId == null || "".equals(sessionId.trim())) {
			System.out
					.println("ERROR: The incoming session id either null or empty.");
			return null;
		}
		SessionTableData session = sessionMap.get(sessionId);
		if (session == null) {
			return null;
		}
		while (session.isInUse()) {
			try {
				wait(100);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		sessionData = sessionMap.get(sessionId);
		return sessionData;

	}

	/**
	 * Method to remove the entry from the session table.
	 */
	public void removeEntry(String sessionId) {
		if (sessionId == null || "".equals(sessionId.trim())) {
			System.out
					.println("ERROR: Empty session id while removing the session data.");
			return;
		}
		SessionTableData session = sessionMap.get(sessionId);
		if (session == null) {
			return;
		}
		while (session.isInUse()) {
			try {
				wait(100);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		// making this part of code synchronized, so that only a single thread
		// can access while the session entry is removed
		sessionMap.remove(sessionId);

	}

	/**
	 * Method to display the session map, added for debugging purpose.
	 */
	public void displaySessionMap() {
		for (Map.Entry<String, SessionTableData> entry : sessionMap.entrySet()) {
			System.out
					.println("**************************************************");
			System.out.println("Key: " + entry.getKey());
			System.out.println("Session Creation Time: "
					+ entry.getValue().getSessionCreationTime());
			System.out.println("Session Val: "
					+ entry.getValue().getSessionVal());
			System.out.println("Session Version Number: "
					+ entry.getValue().getVersionNumber());
			System.out
					.println("**************************************************");
		}
	}

	public static SessionTableData fetchSession(String session) {
		String[] sessionData = session.split(" ");
		try {
			// RPCClientService rpcClient = new RPCClientService();
			String values = rpcClient.RpcClientGet(sessionData[0], new Double(
					sessionData[1]).doubleValue());

			if (values == null)
				return null;

			String[] sessionValues = values
					.split(LSIConstants.PACKET_DATA_DELIMITER);
			if (sessionValues == null || sessionValues.length != 7) {
				return null;
			}

			for (String str : sessionValues) {
				System.out.println("------------ " + str);
			}

			SessionTableData sessionObject = createSessionObject(
					sessionValues[2],
					new Double(sessionValues[3]).doubleValue(),
					sessionValues[4], new Integer(sessionValues[5]).intValue(),
					sessionValues[6]);

			return sessionObject;

		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public static SessionTableData createSessionObject(String sessionId,
			double sessionVer, String sessionCreationTime, int counter,
			String sessionVal) {
		SessionTableData sessionData = new SessionTableData();
		sessionData.setSessionCreationTime(sessionCreationTime);
		sessionData.setSessionID(sessionId);
		sessionData.setVersionNumber(sessionVer);
		sessionData.setCounter(counter);
		sessionData.setSessionVal(sessionVal);
		return sessionData;
	}

	public static String updateSession(SessionTableData session) {

		try {
			// RPCClientService rpcClient = new RPCClientService();
			String[] lists = rpcClient.RpcClientPut(session.getSessionID(),
					session.getVersionNumber(),
					session.getSessionCreationTime(), session.getCounter(),
					session.getSessionVal());
			StringBuffer sb = new StringBuffer();
			if (lists == null || lists.length == 2)
				return null;
			for (int count = 0; count < lists.length; count++) {
				sb.append(lists[count]);
				sb.append("_");
			}
			return sb.toString();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public static void deleteSession(String session, String serverList) {

		try {
			// RPCClientService rpcClient = new RPCClientService();
			rpcClient.RpcClientDel(session, serverList);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}
}